1 //
2 // Copyright (C) 2021 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 //! ProfCollect configurations.
18 
19 use anyhow::Result;
20 use macaddr::MacAddr6;
21 use once_cell::sync::Lazy;
22 use rand::Rng;
23 use serde::{Deserialize, Serialize};
24 use std::error::Error;
25 use std::fs::{read_dir, remove_file};
26 use std::path::Path;
27 use std::process::Command;
28 use std::str::FromStr;
29 use std::time::Duration;
30 
31 const PROFCOLLECT_CONFIG_NAMESPACE: &str = "aconfig_flags.profcollect_native_boot";
32 const PROFCOLLECT_NODE_ID_PROPERTY: &str = "persist.profcollectd.node_id";
33 
34 const DEFAULT_BINARY_FILTER: &str = "(^/(system|apex/.+|vendor)/(bin|lib64)/.+)|\
35     (^/data/app/.+\\.so$)|kernel.kallsyms";
36 pub const REPORT_RETENTION_SECS: u64 = 14 * 24 * 60 * 60; // 14 days.
37 
38 // Static configs that cannot be changed.
39 pub static TRACE_OUTPUT_DIR: Lazy<&'static Path> =
40     Lazy::new(|| Path::new("/data/misc/profcollectd/trace/"));
41 pub static PROFILE_OUTPUT_DIR: Lazy<&'static Path> =
42     Lazy::new(|| Path::new("/data/misc/profcollectd/output/"));
43 pub static REPORT_OUTPUT_DIR: Lazy<&'static Path> =
44     Lazy::new(|| Path::new("/data/misc/profcollectd/report/"));
45 pub static CONFIG_FILE: Lazy<&'static Path> =
46     Lazy::new(|| Path::new("/data/misc/profcollectd/output/config.json"));
47 pub static LOG_FILE: Lazy<&'static Path> =
48     Lazy::new(|| Path::new("/data/misc/profcollectd/output/trace.log"));
49 
50 /// Dynamic configs, stored in config.json.
51 #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
52 pub struct Config {
53     /// Version of config file scheme, always equals to 1.
54     version: u32,
55     /// Application specific node ID.
56     pub node_id: MacAddr6,
57     /// Device build fingerprint.
58     pub build_fingerprint: String,
59     /// Interval between collections.
60     pub collection_interval: Duration,
61     /// An optional filter to limit which binaries to or not to profile.
62     pub binary_filter: String,
63     /// Maximum size of the trace directory.
64     pub max_trace_limit_mb: u64,
65     /// The kernel release version
66     pub kernel_release: String,
67 }
68 
69 impl Config {
from_env() -> Result<Self>70     pub fn from_env() -> Result<Self> {
71         Ok(Config {
72             version: 1,
73             node_id: get_or_initialise_node_id()?,
74             build_fingerprint: get_build_fingerprint()?,
75             collection_interval: Duration::from_secs(get_device_config(
76                 "collection_interval",
77                 600,
78             )?),
79             binary_filter: get_device_config("binary_filter", DEFAULT_BINARY_FILTER.to_string())?,
80             max_trace_limit_mb: get_device_config("max_trace_limit_mb", 768)?,
81             kernel_release: get_kernel_release(),
82         })
83     }
84 }
85 
86 impl std::fmt::Display for Config {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result87     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88         write!(f, "{}", serde_json::to_string(self).expect("Failed to deserialise configuration."))
89     }
90 }
91 
92 impl FromStr for Config {
93     type Err = serde_json::Error;
from_str(s: &str) -> Result<Self, Self::Err>94     fn from_str(s: &str) -> Result<Self, Self::Err> {
95         serde_json::from_str::<Config>(s)
96     }
97 }
98 
get_or_initialise_node_id() -> Result<MacAddr6>99 fn get_or_initialise_node_id() -> Result<MacAddr6> {
100     let mut node_id = get_property(PROFCOLLECT_NODE_ID_PROPERTY, MacAddr6::nil())?;
101     if node_id.is_nil() {
102         node_id = generate_random_node_id();
103         set_property(PROFCOLLECT_NODE_ID_PROPERTY, node_id)?;
104     }
105 
106     Ok(node_id)
107 }
108 
get_build_fingerprint() -> Result<String>109 fn get_build_fingerprint() -> Result<String> {
110     get_property("ro.build.fingerprint", "unknown".to_string())
111 }
112 
get_device_config<T>(key: &str, default_value: T) -> Result<T> where T: FromStr + ToString, T::Err: Error + Send + Sync + 'static,113 fn get_device_config<T>(key: &str, default_value: T) -> Result<T>
114 where
115     T: FromStr + ToString,
116     T::Err: Error + Send + Sync + 'static,
117 {
118     let default_value = default_value.to_string();
119     let config =
120         flags_rust::GetServerConfigurableFlag(PROFCOLLECT_CONFIG_NAMESPACE, key, &default_value);
121     Ok(T::from_str(&config)?)
122 }
123 
get_sampling_period() -> Duration124 pub fn get_sampling_period() -> Duration {
125     let default_period = 1500;
126     Duration::from_millis(
127         get_device_config("sampling_period", default_period).unwrap_or(default_period),
128     )
129 }
130 
get_property<T>(key: &str, default_value: T) -> Result<T> where T: FromStr + ToString, T::Err: Error + Send + Sync + 'static,131 fn get_property<T>(key: &str, default_value: T) -> Result<T>
132 where
133     T: FromStr + ToString,
134     T::Err: Error + Send + Sync + 'static,
135 {
136     let default_value = default_value.to_string();
137     let value = rustutils::system_properties::read(key).unwrap_or(None).unwrap_or(default_value);
138     Ok(T::from_str(&value)?)
139 }
140 
set_property<T>(key: &str, value: T) -> Result<()> where T: ToString,141 fn set_property<T>(key: &str, value: T) -> Result<()>
142 where
143     T: ToString,
144 {
145     let value = value.to_string();
146     Ok(rustutils::system_properties::write(key, &value)?)
147 }
148 
generate_random_node_id() -> MacAddr6149 fn generate_random_node_id() -> MacAddr6 {
150     let mut node_id = rand::thread_rng().gen::<[u8; 6]>();
151     node_id[0] |= 0x1;
152     MacAddr6::from(node_id)
153 }
154 
get_kernel_release() -> String155 fn get_kernel_release() -> String {
156     match Command::new("uname").args(["-r"]).output() {
157         Ok(output) if output.status.success() => {
158             String::from_utf8_lossy(&output.stdout).trim().to_string()
159         }
160         _ => String::new(),
161     }
162 }
163 
clear_data() -> Result<()>164 pub fn clear_data() -> Result<()> {
165     fn remove_files(path: &Path) -> Result<()> {
166         read_dir(path)?
167             .filter_map(|e| e.ok())
168             .map(|e| e.path())
169             .filter(|e| e.is_file() && e != *LOG_FILE)
170             .try_for_each(remove_file)?;
171         Ok(())
172     }
173 
174     remove_files(&TRACE_OUTPUT_DIR)?;
175     remove_files(&PROFILE_OUTPUT_DIR)?;
176     remove_files(&REPORT_OUTPUT_DIR)?;
177     Ok(())
178 }
clear_processed_files() -> Result<()>179 pub fn clear_processed_files() -> Result<()> {
180     read_dir(&PROFILE_OUTPUT_DIR as &Path)?
181         .filter_map(|e| e.ok())
182         .map(|e| e.path())
183         .filter(|e| e.is_file() && e != (&CONFIG_FILE as &Path))
184         .try_for_each(remove_file)?;
185     Ok(())
186 }
187